"""

.. module:: admintask
.. moduleauthor:: @author: 

The purpose of this module is ... 
   
.. seealso:: modules :mod:`<module>`

*Constants*:
.. data:: constant_1
        
    constant_1 is used for ......
        
.. data:: constant_2
        
    constant_2 is used for ......
"""

# Imports
#
import logging

from runtime.task import Task
from datastore.resource import Resource

# Constants
#

# Classes
#

class AdminTask(Task):
    """This class is started from Runtime. It is responsible for:
    
    * loading all 'sites' definitions from the datastore
    * associating an IP address and port number with each site.
    * starting HTTP tasks for each site.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    def __init__(self, ds, spaces, task_uri, task_def_rs):
        # <purpose>
        #
        # Parameters:
        #     ds : datastore.database.DataStore
        #     spaces : runtime.spaces.Spaces - Reference to spaces
        #     task_uri : String - the uri for this task. The root uri will most
        #                likely be the 'binding' uri for this task.
        #     task_def_rs : datastore.resource.Resource - Task definition resource.
        #
        
        Task.__init__(self, ds, spaces, task_uri, task_def_rs)
        
        self.logger = logging.getLogger('yarra.services.admintask.AdminTask')
        self.logger.log(logging.DEBUG, "ds (%s), spaces (%s), task_uri (%s), task_def_rs (%s)" % (ds, spaces, task_uri, task_def_rs.pretty_print()))
        
        # This is an array of dictionaries. The array is indexed by the port 
        # offset, and each dictionary stores the title and description of each
        # site. This allows us to apply multiple sites to each port.
        #
#        self.normalised_site_array = []
        
        # get the node configuration from the datastore so we can assign port 
        # numbers to sites.
        #  
        self.configuration_rs = self.ds.find([(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.LOCALHOST_CONFIGURATION)])[0]
        self.logger.log(logging.DEBUG, "configuration_rs (%s)" % self.configuration_rs.pretty_print())
        
        
    def cb_start(self, method, uri, new_uri, result, context, **kwargs):
        """This function is called when the task starts. 
        
        :param method: <class> or <type>
        :param uri: The task Uri.
        :param new_uri: <class> or <type>
        :param result: <class> or <type>
        :param context: <class> or <type>
        :param kwargs: <class> or <type>
        """
        
        self.logger.log(logging.DEBUG, "uri (%s), new_uri (%s)" % (uri, new_uri))
        
        # 1. load all 'sites' definitions - there may be many!
        # 2. associate an IP address and port number with each site.
        # 3. send an 'add_site' message to HTTPTask for each site.
        #
        site_rs_list = self.ds.find([(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE, Resource.KEY_URI_PACKAGE_TYPE_SITE)])
        for site_rs in site_rs_list:
            self.logger.log(logging.DEBUG, "site (%s)" % site_rs.pretty_print())
            self.logger.log(logging.DEBUG, "site vars (%s)" % site_rs.get_vars())
            
            # Fields that should be set in the site definition from day one.
            #
            title = ""
            description = ""
            portoffset = 0
            default = ""
            templateuri = ""
            actions_list = []
            transforms_dict = {}
            is_new_port = False
            
            l = site_rs.get_metadata(Resource.KEY_URI_DUBLINCORE_TITLE)
            if (len(l) > 0):
                title = l[0]
            l = site_rs.get_metadata(Resource.KEY_URI_DUBLINCORE_DESCRIPTION)
            if (len(l) > 0):
                description = l[0]
            portoffset = site_rs.get_var(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_PORTOFFSET)
            default = site_rs.get_var(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_DEFAULT)
            templateuri = site_rs.get_var(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_TEMPLATEURI)
            actions_list = site_rs.get_var(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_ACTIONS)
            transforms_dict = site_rs.get_var(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_TRANSFORMS)
            
            self.logger.log(logging.DEBUG, "site title (%s)" % title)
            self.logger.log(logging.DEBUG, "site description (%s)" % description)
            self.logger.log(logging.DEBUG, "site port offset (%s)" % portoffset)
            self.logger.log(logging.DEBUG, "site default (%s)" % default)
            self.logger.log(logging.DEBUG, "site template uri (%s)" % templateuri)
            self.logger.log(logging.DEBUG, "site actions list (%s)" % actions_list)
            self.logger.log(logging.DEBUG, "site templates dictionary (%s)" % transforms_dict)
            
            # Fields that should be set in the site definition the first time this
            # function is run.
            #
            port = 0
            
            try:
                port = site_rs.get_var(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_PORT)
            except KeyError:
                self.logger.log(logging.ERROR, "Non-existant 'port' in 'site' (%s) XML." % title)
                
            if (len(title) == 0):
                self.logger.log(logging.ERROR, "No 'site' title defined. Ignoring site.")
            else:
                # If port# has not yet been assigned to this site then this is
                # the first call (ever) of cb_start(). So, let's assign the port#
                # and write back to the datastore after processing all sites in
                # the current site resource.
                #
                # Note: ip-address is always taken from the configuration resource,
                #       not the site resource.
                #
                if (port == 0):
                    self.logger.log(logging.DEBUG, "************ No port number found in site (%s) definition. Assigning..." % title)
                    
                    # manufacture port number for site...
                    #
                    port = self.configuration_rs.get_single_metadata(Resource.KEY_URI_DATASTORE_CONFIG_NODE_PORTRANGELOW) + portoffset;
                    site_rs.set_var(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_PORT, port)
                    is_new_port = True

                # Create and populate the Resource to send to HTTPTask to start 
                # a listener for this IP-address/port# combo.
                #
                rs = Resource()
                rs.set_metadata(Resource.KEY_URI_DUBLINCORE_TITLE, title)
                rs.set_metadata(Resource.KEY_URI_DUBLINCORE_DESCRIPTION, description)
                rs.set_metadata(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_IPADDRESS, self.configuration_rs.get_single_metadata(Resource.KEY_URI_DATASTORE_CONFIG_NODE_IPADDRESS))
                rs.set_metadata(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_PORT, port)
                rs.set_metadata(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_DEFAULT, default)
                rs.set_metadata(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_TEMPLATEURI, templateuri)
                rs.set_metadata(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_ACTIONS, actions_list)
                rs.set_metadata(Resource.KEY_URI_SERVICES_TYPE_HTTP_SITE_TRANSFORMS, transforms_dict)

                # destination of message - HTTPTask
                # - 'listener' is appended to HTTPTask Uri because HTTPTask 
                #   is waiting on 'task:base/service/httptask/*' but won't see
                #   messages to 'task:base/service/httptask'.
                #
                http_uri = self.spaces_context.uri(Resource.TASK_URI_HTTP)
                
                # send 'add_site' message to HTTPTask (Any 'put' message 
                # to HTTPTask is interpreted as a site addition.                         
                #
                self.spaces_context.put(http_uri.add("listen"), rs)
            
                # write assigned port#s to datastore.
                #
                if (is_new_port is True):
                    self.ds.put(site_rs)
            
    def cb_stop(self, method, uri, res, result, context, **kwargs):
        """This function will stop any listeners still running.
        
        :param method: <class> or <type>
        :param uri: The task Uri.
        :param res: <class> or <type>
        :param result: <class> or <type>
        :param context: <class> or <type>
        :param kwargs: <class> or <type>
        """
        
        self.logger.log(logging.DEBUG, "method (%s), uri (%s)" % (method, uri))